
#include <material_editor/materialview.h>
#include <material_editor/programeditor.h>
#include <material_editor/selectmaterialdatadialog.h>
#include <session.h>
#include <stdio.h>
#include <genericdeletedialog.h>
#include <genericrenamedialog.h>

#include <gfx/programparametername.h>
#include <gfx/iprogram.h>

#include <QAbstractItemModel>
#include <QComboBox>
#include <QItemDelegate>


class ProgramShaderProgramListModel : public QAbstractItemModel
{
private:
  Program* _program;

public:
  ProgramShaderProgramListModel ()
    : _program(0)
  {

  }

  void SetProgram (Program* program)
  {
    _program = program;
  }

  Program* GetProgram () const
  {
    return _program;
  }


  void Changed ()
  {
    beginResetModel();
    reset ();
    endResetModel();
  }

  virtual int columnCount (const QModelIndex& parent = QModelIndex()) const
  {
    return 1;
  }

  virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const
  {
    if (!_program) return QVariant();
    if (!index.isValid()) return QVariant();
    if (role != Qt::DisplayRole) return QVariant ();

    Program::ShaderProgram* prog = static_cast<Program::ShaderProgram*>(index.internalPointer());
    if (!prog) return QVariant ();

    QString name = prog->GetName();
    return QVariant (name);

  }

  virtual QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const
  {
    if (!_program) return QModelIndex();
    if (parent.isValid()) return QModelIndex ();
    if (column != 0) return QModelIndex();

    return createIndex(row, column, _program->GetProgram(row));

  }

  virtual QModelIndex parent (const QModelIndex& index) const
  {
    return QModelIndex ();
  }

  virtual int rowCount (const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return 0;
    if (!_program) return 0;
    return _program->GetNumberOfPrograms();
  }

};

class ProgramBindingModel : public QAbstractItemModel
{
private:
  Program* _program;

public:
  ProgramBindingModel ()
    : _program(0)
  {

  }
  void SetProgram (Program* program)
  {
    _program = program;
  }

  Program* GetProgram () const
  {
    return _program;
  }


  void Changed ()
  {
    beginResetModel();
    reset ();
    endResetModel();
  }

  virtual int columnCount (const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return 0;
    return 2;
  }

  const QString& GetBinding (int row)
  {
    return _program->GetBindingValue(row);
  }

  virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const
  {
    if (!_program) return QVariant();
    if (!index.isValid()) return QVariant();
    if (role != Qt::DisplayRole && role !=  Qt::EditRole) return QVariant ();
    if (index.row() >= _program->GetNumberOfBindings()) return QVariant ();

    switch (index.column())
      {
      case 0:
        return QVariant (_program->GetBindingKey(index.row()));
      case 1:
        return QVariant (_program->GetBindingValue(index.row()));
      default:
        break;
      }

    return QVariant ();

  }

  virtual QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const
  {
    if (!_program) return QModelIndex();
    if (parent.isValid()) return QModelIndex ();
    if (column < 0 || column >= 2) return QModelIndex();

    return createIndex(row, column);

  }

  virtual QModelIndex parent (const QModelIndex& index) const
  {
    return QModelIndex ();
  }

  virtual int rowCount (const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return 0;
    if (!_program) return 0;
    return _program->GetNumberOfBindings();
  }

  virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
  {
    if (role != Qt::DisplayRole) return QVariant ();
    switch (section)
      {
      case 0:
        return QVariant ("Value");
      case 1:
        return QVariant ("Binding");
      default:
        break;
      }
    return QVariant ();
  }

  Qt::ItemFlags flags (const QModelIndex &index) const
  {
    if (!index.isValid()) return 0;
    switch (index.column())
      {
      case 0:
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
      case 1:
        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
      default:
        break;
      }
    return 0;
  }

  bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)
  {
    if (!index.isValid() || index.column() != 1) return false;

    _program->SetBindingValue(index.row(), value.toString());
    return true;
  }
};


class ProgramErrorModel : public QAbstractItemModel
{
private:
	enum 
	{
		C_ProgramName,
    C_ShaderName,
		C_InfoType,
		C_Line,
		C_Message,
		C_COUNT
	};

	struct Info
	{
    QString	programName;
    Shader* shader;
    QString infoType;
    QString	line;
    QString message;
	};

	QList<Info>	_infos;

public:
  ProgramErrorModel ()
  {

  }

	void Clear ()
	{
		_infos.clear ();
	}

  void AddInfo (const QString& program, Shader* shader, const QString& infoType, const QString& line, QString& message)
	{
		Info info;
    info.programName = program;
    info.shader = shader;
    info.infoType = infoType;
    info.line = line;
    info.message = message;
    fflush(stdout);
		_infos.push_back(info);
	}

  void Changed ()
  {
    beginResetModel();
    reset ();
    endResetModel();
  }

  virtual int columnCount (const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return 0;
    return C_COUNT;
  }

  virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const
  {
    if (!index.isValid()) return QVariant();
    if (index.row() >= _infos.size()) return QVariant ();
    const Info& info = _infos[index.row()];
    if (role == Qt::DisplayRole)
      {
        switch (index.column())
          {
          case C_ProgramName:
            return QVariant (info.programName);
          case C_ShaderName:
            return QVariant (info.shader->GetFullname());
          case C_InfoType:
            return QVariant (info.infoType);
          case C_Line:
            return QVariant (info.line);
          case C_Message:
            return QVariant (info.message);
          default:
            break;
          }
      }
    else if (role == Qt::BackgroundRole)
      {
        if (info.infoType == "warning")
          {
            return QVariant (QColor (255, 255, 0));
          }
        else if (info.infoType == "error")
          {
            return QVariant (QColor (255, 0, 0));
          }
        return QVariant();
      }
    return QVariant ();

  }

  virtual QModelIndex index (int row, int column, const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return QModelIndex ();
    if (column < 0 || column >= C_COUNT) return QModelIndex();

    return createIndex(row, column);

  }

  virtual QModelIndex parent (const QModelIndex& index) const
  {
    return QModelIndex ();
  }

  virtual int rowCount (const QModelIndex& parent = QModelIndex()) const
  {
    if (parent.isValid()) return 0;
		return _infos.size();
  }

  virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const
  {
    if (role != Qt::DisplayRole) return QVariant ();
    switch (section)
      {
			case C_ProgramName:
				return QVariant ("Program");
      case C_ShaderName:
        return QVariant ("Shader");
      case C_InfoType:
        return QVariant ("Level");
      case C_Line:
        return QVariant ("Line");
      case C_Message:
        return QVariant ("Message");
      default:
        break;
      }
    return QVariant ();
  }

  unsigned GetNumberOfInfos ()
  {
    return _infos.size();
  }

  Shader* GetShader (int idx)
  {
    return _infos[idx].shader;
  }

  int GetLine (int idx)
  {
    QString line = _infos[idx].line;
    return line.toInt();
  }

};

class ProgramBindingComboBox : public QComboBox
{
public:
  ProgramBindingComboBox (QWidget* parent = 0)
    : QComboBox (parent)
  {
    ceProgramParameterNameManager* mng = ceProgramParameterNameManager::Get();
    for (unsigned i=0, j=mng->GetNumberOfNames(); i<j; ++i)
      {
        addItem(QString(mng->GetName(i).c_str()));
      }

    setAutoFillBackground(true);
    setEditable(true);
  }
};

class ProgramBindingItemDelegate : public QItemDelegate
{
private:
  ProgramBindingModel* _model;
  int _height;
public:
  ProgramBindingItemDelegate (ProgramBindingModel* model)
    : QItemDelegate()
    , _model (model)
  {
    _height = 18;
  }

  virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
  {
    return new ProgramBindingComboBox(parent);
  }

  virtual void setEditorData(QWidget *editor, const QModelIndex &index) const
  {
    QComboBox* comboBox = static_cast<QComboBox*>(editor);
    const QString& binding = _model->GetBinding(index.row());
    for (int i=0, j=comboBox->count(); i<j; ++i)
      {
        if (comboBox->itemText(i) == binding)
          {
            comboBox->setCurrentIndex(i);
            return;
          }
      }
    comboBox->setEditText(binding);
  }

  virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
  {
    QComboBox* comboBox = static_cast<QComboBox*>(editor);
    model->setData(index, QVariant (comboBox->currentText()), Qt::EditRole);
  }

  virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
  {
    return QSize (-1, _height);
  }

};


ProgramEditor::ProgramEditor(Program* program, MaterialView* view, QWidget *parent)
  : BaseMaterialEditorWidget(parent)
  , _program(0)
  , _materialView(view)
{
  _gui.setupUi(this);
  _groupView = new GroupTreeItemView(this);
  QGridLayout* layout = new QGridLayout(_gui.widgetGroupView);
  layout->setContentsMargins(QMargins (0, 0, 0, 0));
  layout->addWidget (_groupView, 0, 0);

  _programModel = new ProgramShaderProgramListModel ();
  _shaderDelegator = new AllProgramShaderDelegator ();
  _shaderModel = new MaterialDataListModel ();
  _shaderModel->SetDelegator(_shaderDelegator);

  _bindingModel = new ProgramBindingModel();
	_errorModel = new ProgramErrorModel();
  _bindingDelegate = new ProgramBindingItemDelegate(_bindingModel);


  _gui.listViewPrograms->setModel(_programModel);
  _gui.listViewShaders->setModel(_shaderModel);
  _gui.treeViewBindings->setModel(_bindingModel);
  _gui.treeViewBindings->setItemDelegate(_bindingDelegate);
  _gui.treeViewError->setModel(_errorModel);

  if (program)
    {
      SetProgram(program);
    }

  connect (_gui.listViewPrograms->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
           this, SLOT(ProgramSelectionChanged(QModelIndex,QModelIndex)));
  connect (_gui.listViewShaders->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
           this, SLOT(ShaderSelectionChanged(QModelIndex,QModelIndex)));
}


Program::ShaderProgram* ProgramEditor::GetCurrentProgram()
{
  QModelIndex index = _gui.listViewPrograms->currentIndex();
  return static_cast<Program::ShaderProgram*>(index.internalPointer());
}

void ProgramEditor::UpdateGUI()
{
}


void ProgramEditor::SetProgram(Program *program)
{
  _program = program;
  _groupView->SetGroupTreeItem(&program->GetCategory());

  _programModel->SetProgram(_program);
  _bindingModel->SetProgram(_program);

  SetItem (program);
  UpdateGUI();
  SetDirty (false);
}

Program* ProgramEditor::GetProgram ()
{
  return _program;
}


void ProgramEditor::ContentChanged ()
{
  SetDirty(true);
}

void ProgramEditor::Save ()
{
  SetDirty(false);
}

void ProgramEditor::Compile()
{
  Save ();
	_errorModel->Clear ();

  for (unsigned i=0, j=_program->GetNumberOfPrograms(); i<j; ++i)
    {
      Program::ShaderProgram* prog = _program->GetProgram(i);
      iProgram* program = prog->GetProgram();
      program->ClearShaders();

      for (unsigned k=0, l=prog->GetNumberOfShaders(); k<l; ++k)
        {
          Shader* shader = prog->GetShader(k);

          iShader* shd = shader->GetShader();
          shd->Compile();
          ParseAndAddInfoLog (prog->GetName(), shader, QString (shd->GetCompileInfoLog().c_str()));

          program->AttachShader(shd);
        }
      program->Link();
    }
  fflush(stdout);
  _errorModel->Changed();
}

void ProgramEditor::ParseAndAddInfoLog (const QString& programName, Shader* shader, const QString& infoLog)
{
  QStringList lines = infoLog.split("\n", QString::SkipEmptyParts);
  QRegExp logExp ("\\d+\\((\\d+)\\)\\s*:\\s*(error|warning|info)(.*)", Qt::CaseInsensitive);
  foreach (const QString& line, lines)
    {
      int index = line.indexOf(logExp);
      if (index  == -1) continue;


      QStringList values = logExp.capturedTexts();
      if (values.size() != 4) continue;
      _errorModel->AddInfo(programName, shader, values[2], values[1], values[3]);
    }
}

void ProgramEditor::EnableDisableButtons ()
{
  _gui.pushButtonAddProgram->setEnabled (true);
  QModelIndex index = _gui.listViewPrograms->currentIndex ();
  if (!index.isValid() || !index.internalPointer())
    {
      _gui.pushButtonRemoveProgram->setEnabled (false);
      _gui.pushButtonAddShader->setEnabled(false);
      _gui.pushButtonRemoveShader->setEnabled(false);
      return;
    }
  _gui.pushButtonRemoveProgram->setEnabled (true);
  _gui.pushButtonAddShader->setEnabled(true);

  index = _gui.listViewShaders->currentIndex();
  if (!index.isValid() || !index.internalPointer())
    {
      _gui.pushButtonRemoveShader->setEnabled(false);
      return;
    }
  _gui.pushButtonRemoveShader->setEnabled(true);
}

void ProgramEditor::ProgramChanged()
{
  QModelIndex index = _gui.listViewPrograms->currentIndex();

  Program::ShaderProgram* oldProgram = static_cast<Program::ShaderProgram*>(index.internalPointer());
  _programModel->Changed();

  _program->UpdateBindingValues();
  _bindingModel->Changed();


  int newRow = index.row();
  if (newRow >= _program->GetNumberOfPrograms())
    {
      newRow--;
    }

  if (newRow < 0)
    {
      _shaderDelegator->SetProgram(0);
      ShaderChanged();
      EnableDisableButtons();
      return;
    }

  index = _programModel->index(newRow, 0);
  Program::ShaderProgram* newProgram = static_cast<Program::ShaderProgram*>(index.internalPointer());
  if (newProgram != oldProgram)
    {
      _gui.listViewPrograms->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
      EnableDisableButtons();
    }
}

void ProgramEditor::ShaderChanged()
{
  QModelIndex index = _gui.listViewShaders->currentIndex();
  _shaderModel->Changed();

  _program->UpdateBindingValues();
  _bindingModel->Changed();

  Program::ShaderProgram* prog = GetCurrentProgram();
  if (!prog)
    {
      EnableDisableButtons();
      return;
    }

  int newRow = index.row();
  if (newRow >= prog->GetNumberOfShaders())
    {
      newRow--;
    }

  if (newRow < 0)
    {
      EnableDisableButtons();
      return;
    }

  index = _shaderModel->index(newRow, 0);
  _gui.listViewShaders->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);

  EnableDisableButtons();

}

void ProgramEditor::ProgramSelectionChanged(QModelIndex index, QModelIndex)
{
  EnableDisableButtons();

  Program::ShaderProgram* prog = GetCurrentProgram();
  if (!prog)
    {
      return;
    }
  _shaderDelegator->SetProgram(prog);
  _shaderModel->Changed();
}

void ProgramEditor::ShaderSelectionChanged(QModelIndex index, QModelIndex)
{
  EnableDisableButtons();
}

void ProgramEditor::on_pushButtonAddProgram_clicked()
{
  if (!_program) return;

  QString name = GenericRenameDialog::GetName(Session::Get()->GetMainWindow(),
                                              "Add new shaderprogram",
                                              "",
                                              _program->GetName());
  if (name.isNull())
    {
      return;
    }


  Program::ShaderProgram* prog = _program->CreateProgram();
  prog->SetName(name);
  int row = _program->AddProgram(prog);
  ProgramChanged();

  QModelIndex index = _programModel->index(row, 0);
  _gui.listViewPrograms->setCurrentIndex(index);
}

void ProgramEditor::on_pushButtonRemoveProgram_clicked()
{
  if (!_program) return;
  QModelIndex index = _gui.listViewPrograms->currentIndex ();
  if (!index.isValid() || !index.internalPointer())
    {
      EnableDisableButtons();
      return;
    }
  Program::ShaderProgram* prog = static_cast<Program::ShaderProgram*>(index.internalPointer());
  if (GenericDeleteDialog::Delete(Session::Get()->GetMainWindow(),
                                  "Delete shderprogram",
                                  QString ("Are you sure to delete the shaderprogram '%1'?)").arg(prog->GetName())))
    {
      _program->RemoveProgram(prog);
      ProgramChanged();
    }

}


void ProgramEditor::on_pushButtonAddShader_clicked()
{
  Program::ShaderProgram* prog = GetCurrentProgram();
  if (!prog)
    {
      return;
    }

  SelectMaterialDataDialog dlg (Session::Get()->GetMainWindow());
  AllShaderDelegator delegator (Session::Get()->GetMaterialManager());
  dlg.SetDelegator(&delegator);
  if (dlg.exec() != QDialog::Accepted)
    {
      return;
    }

  Shader* shader = static_cast<Shader*>(dlg.GetData());
  if (!shader)
    {
      return;
    }

  prog->AddShader(shader);
  ShaderChanged();
}

void ProgramEditor::on_pushButtonRemoveShader_clicked()
{
  if (!_program) return;

  Program::ShaderProgram* prog = GetCurrentProgram();
  if (!prog)
    {
      EnableDisableButtons();
      return;
    }

  QModelIndex index = _gui.listViewShaders->currentIndex ();
  if (!index.isValid() || !index.internalPointer())
    {
      EnableDisableButtons();
      return;
    }
  Shader* shader= static_cast<Shader*>(index.internalPointer());
  prog->RemoveShader(shader);
  ShaderChanged();

}


void ProgramEditor::on_pushButtonUpdate_clicked()
{
  if (!_program) return;
  _program->UpdateBindingValues();
  _bindingModel->Changed();
}

void ProgramEditor::on_pushButtonAutoAssign_clicked ()
{
  if (!_program) return;

  _program->AutoAssignBindings();
  _bindingModel->Changed();

}


void ProgramEditor::on_listViewShaders_doubleClicked(const QModelIndex &index)
{
  if (!index.isValid() || !index.internalPointer())
    {
      return;
    }
  Shader* shader= static_cast<Shader*>(index.internalPointer());
  _materialView->EditShader(shader);
}

void ProgramEditor::on_treeViewError_doubleClicked(const QModelIndex &index)
{
  if (!index.isValid())
    {
      return;
    }

  int row = index.row();
  if (row < 0 || row >=_errorModel->GetNumberOfInfos())
    {
      return;
    }
  Shader* shader = _errorModel->GetShader(row);
  int line = _errorModel->GetLine(row);
  _materialView->EditShader(shader, line);
}
